using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using Microsoft.Phone.Tasks;

namespace Columbus
{
    public abstract class ViewModel : PropertyChangedNotifier
    {
        private readonly List<string> nonRestorableProperties = new List<string>();
        private readonly List<Type> nonRestorableTypes = new List<Type>(new [] { typeof(ICommand), typeof(NavigationAppication) });

        public INavigator Navigator { get; set; }
        public IApplicationSettings ApplicationSettings { get; internal set; }

        protected ViewModel()
        { }

        protected ViewModel(IApplicationSettings applicationSettings)
        {
            ApplicationSettings = applicationSettings;
        }

        public void NonRestorable<T>(Expression<Func<T>> propertySelector)
        {
            nonRestorableProperties.Add(propertySelector.GetPropertyName());
        }

        public T GetSetting<T>(Expression<Func<T>> property, T fallback)
        {
            var propertyName = property.GetPropertyName();
            if (ApplicationSettings.Contains(propertyName))
                return (T)ApplicationSettings[propertyName];

            return fallback;
        }

        public void SaveSetting<T>(Expression<Func<T>> property, T value)
        {
            var propertyName = property.GetPropertyName();

            if (ApplicationSettings.Contains(propertyName))
                ApplicationSettings[propertyName] = value;
            else
                ApplicationSettings.Add(propertyName, value);

            NotifyPropertyChanged(property);
        }

        public void Deactivate(IViewState state)
        {
            OnDeactivated(state);
        }

        protected virtual void OnDeactivated(IViewState state)
        {
            SetNonRestorable();
            foreach (var property in GetType().GetProperties().Where(IsRestorable))
            {
                state.Save(property.GetViewStateKey(), property.GetValue(this, null));
            }
        }

        public void Activate(IViewState state, IApplicationSettings applicationSettings)
        {
            OnActivated(state, applicationSettings);
            if (Chooser.LastResult != null)
                Chooser.LastResult.Complete(this);
        }

        protected virtual void OnActivated(IViewState state, IApplicationSettings applicationSettings)
        {
            SetNonRestorable();
            ApplicationSettings = applicationSettings;
            foreach (var property in GetType().GetProperties().Where(IsRestorable))
            {
                property.SetValue(this, state.Get(property.GetViewStateKey()), null);
            }
        }

        public void LaunchTask<TChooser, TTaskEventArgs>(Expression<Action<TTaskEventArgs>> onComplete)
            where TChooser : ChooserBase<TTaskEventArgs>, new()
            where TTaskEventArgs : TaskEventArgs
        {
            var bodyString = onComplete.ToString();
            var body = onComplete.Body as MethodCallExpression;
            if (body == null) throw new InvalidChooserOnCompleteHandlerException(bodyString);
            if (body.Method.DeclaringType != GetType()) throw new InvalidChooserOnCompleteHandlerException(bodyString);
            if (!body.Method.IsPublic) throw new InvalidChooserOnCompleteHandlerException(bodyString);
            if (body.Method.IsStatic) throw new InvalidChooserOnCompleteHandlerException(bodyString);
            if (body.Method.GetParameters().Count() != 1) throw new InvalidChooserOnCompleteHandlerException(bodyString);
            if (body.Method.GetParameters().Single().ParameterType != typeof(TTaskEventArgs)) throw new InvalidChooserOnCompleteHandlerException(bodyString);

            var chooser = new TChooser();
            chooser.Completed += (x, y) => Chooser.LastResult = new ChooserResult(y, Navigator, body.Method, GetType());
            Chooser.Show(chooser.Show);
        }

        private void SetNonRestorable()
        {
            NonRestorable(() => Navigator);
            NonRestorable(() => Navigator);
        }

        private bool IsRestorable(PropertyInfo property)
        {
            return property.GetSetMethod() != null && property.GetGetMethod() != null 
                && !nonRestorableProperties.Contains(property.Name)
                && !nonRestorableTypes.Contains(property.PropertyType);
        }
    }
}